LoadXsense

Author

Stan Brouwer

This is my R code and output of the Xsense Dot IMU data. The source code can be found on my github

IMU’s do not start or stop measuring at the exact same moment in time. Thus, the amount of registration per IMU dffered (FreeAcc_X was of differing length). Assuming that the time in SampletimeFine is synchronized, I temporally realigned the data, excluding some of the first or last elements to ensure dataframes are of the same length

Code for loading the data
# Clean workspace and load dependencies
rm(list = ls())
library(tidyr)
library(dplyr)
library(plotly)
library(ggplot2)
library(knitr)
library(lubridate)

# Function to load Xsense data (and convert time to seconds)
LoadXsenseData <- function(directory) {
  files <- list.files(path = directory, full.names = TRUE)  # Searches each marker file
  data_list <- lapply(files, function(file) { # creates a list storing the data
    data <- read.csv(file, header = TRUE, skip = 10) # reads each csv file
    return(data)
  })
  names(data_list) <- paste0("data", seq_along(data_list))  # Names elements to my preference
  
  # Re-align time
  min_time <- min(sapply(data_list, function(df) min(df$SampleTimeFine))) #  Identify the minimum SampleTimeFine value across all data
  adjusted_data_list <- lapply(data_list, function(df) { # Subtract min_time from SampleTimeFine for each dataframe in data_list
    df$SampleTimeFine <- df$SampleTimeFine - min_time
    df$A_abs <- sqrt(df$FreeAcc_X^2 + df$FreeAcc_Y^2 + df$FreeAcc_Z^2)
    df$TimeS <- (df$SampleTimeFine / 1e6) - (df$SampleTimeFine[1] / 1e6) # Converts time to seconds
    return(df)
  })
  
  # Removing first elements of longer list, Find the minimum number of rows among all dataframes
  min_rows <- min(sapply(adjusted_data_list, nrow))
  
  # Remove elements from the beginning of each dataframe in adjusted_data_list
  adjusted_data_list <- lapply(adjusted_data_list, function(df) {
    if (nrow(df) > min_rows) {
      df <- df[(nrow(df) - min_rows + 1):nrow(df), , drop = FALSE]  # Remove elements from the beginning
    }
    return(df)
  })
  
  # Define Markers dataframe
  markers <- data.frame(PacketCounter = adjusted_data_list[[1]]$PacketCounter,
                        SampleTimeFine = adjusted_data_list[[1]]$SampleTimeFine,
                        TimeS = adjusted_data_list[[1]]$TimeS)

  # Add A_abs columns dynamically for each dataframe
  for (i in 1:5) {
    markers[paste0("A_abs", i)] <- adjusted_data_list[[i]]$A_abs
  }

  # Add FreeAcc_X, FreeAcc_Y, FreeAcc_Z columns for each dataframe
  # for (i in 1:5) {
  #   markers[paste0("A_X", i)] <- adjusted_data_list[[i]]$FreeAcc_X
  #   markers[paste0("A_Y", i)] <- adjusted_data_list[[i]]$FreeAcc_Y
  #   markers[paste0("A_Z", i)] <- adjusted_data_list[[i]]$FreeAcc_Z
  # }

  return(markers)  # Return the Markers dataframe
}

####################################################################
#### function to load all different measurements into "markers_list"
LoadMarkers <- function(folderdir) {
  markers <- list()  # Initialize an empty list to store the dataframes
  folders <- list.dirs(folderdir, full.names = TRUE, recursive = FALSE)
  for (i in seq_along(folders)) { # Iterate through each folder
    data <- LoadXsenseData(folders[i])  # Load Xsense data from the current folder
    markers[[paste0("markers", i)]] <- data  # Store the dataframe in the list with a dynamic name
  }
  return(markers)  # Return the list of dataframes
}

markers_list <- LoadMarkers("../../Logs") # Load all data

Normalise plots

Only the data during the lift should be considered. And I want to express time as percentage of the duration of an lift. Lets start by viewing the plot of the absolute acceleration of the barbell: Note that session 1 - 3 are test sessions.

The Y-axis scale differes. Note that for the strict press (session 4) the downward acceleration is almost similar to the upward acceleration. Thus, we are left with double the peaks in A_abs

Also, the difference between strict presses and push jerks becomes obvious when looking at upper leg acceleration:

Letst exlude the data before and after the push jerk. This is the data I want to clip:

cliptime <- data.frame(
  session = c(NA, NA, NA, NA, 5, 6, 7),
  start = c(NA, NA, NA, NA, 8.4, 10.81, 8),
  end = c(NA, NA, NA, NA, 27.35, 33.3, 38)
)
Code for clipping the data
# plot to visually inspect all the data, to determine where to clip. Would like to program this so it automatically identifies the start of movement patterns. 
plot <- plot_ly(markers_list[[5]], x = ~TimeS, y = ~A_abs5, type = 'scatter', mode = 'lines', name = "Barbell") %>%
  add_trace(y = ~A_abs3, name = "Bovenbeen") %>%
  layout(xaxis = list(title = "TimeS"), yaxis = list(title = "A_abs"), title = "Strict Press")
# plot
rm(plot)
clip_data <- function(cliptime, markers_list) {
  sessions <- list()
  for (i in seq_along(markers_list)) {
    session_num <- i
    if (!is.na(cliptime$session[session_num])) { # check if session should be clipped
      filtered_df <- markers_list[[i]] %>%
        filter(TimeS >= cliptime$start[session_num] & TimeS <= cliptime$end[session_num])
      if (!is.null(filtered_df) && nrow(filtered_df) > 0) {
        assign(paste0("session", session_num), filtered_df, envir = .GlobalEnv) 
        sessions[[paste0("session", session_num)]] <- filtered_df
      }
    }
  }
  return(sessions)
}

Lets plot the clipped data.

Now lets focus on only one lift

# Create a list to store the plots
plots <- lapply(seq_along(sessions), function(i) {
  # Plot Times against A_abs for the current dataframe
  plot <- plot_ly(sessions[[i]], x = ~TimeS, y = ~A_abs5, type = 'scatter', mode = 'lines', name = paste("session", i+4)) %>%
  add_trace(y = ~A_abs2, name = "Bovenbeen", line = list(width = 1)) %>%
    layout(xaxis = list(title = "TimeS"), yaxis = list(title = "A_abs"))
  return(plot)
})

# Combine plots into a single interactive plot
combined_plot <- subplot(plots, nrows = length(plots))
combined_plot